﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using DSLisp.Runtime;
using System.Threading;
using DSLisp.AST;
using System.Linq;
using DSLisp.Infrastructure;

namespace LispGUI
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        Engine engine = null;
        string code = null;
        Thread debugThread = null;
        private DataTable callStackTable = null;
        private object[] allSymbols = null;
        private List<BreakPoint> breakPoints = new List<BreakPoint> ();
        private bool stepping = true;

        private void StartDebugger()
        {
            try
            {
                engine.Execute();
            }
            catch (Exception x)
            {
                MessageBox.Show(x.Message);
            }
            NotifyDone(this,EventArgs.Empty);
        }

        private void RemoveFormatting()
        {
            btnStepInto.Focus();
            int index = txtCode.SelectionStart;
            txtCode.SelectAll();
            txtCode.SelectionColor = Color.Black;
            txtCode.SelectionBackColor = Color.White;
            txtCode.SelectionStart = index;
            txtCode.SelectionLength = 0;
        }

        private void ShowBreakPoints()
        {
            int index = txtCode.SelectionStart;
            foreach (BreakPoint bp in breakPoints)
            {
                if (bp.CodeName != codeName)
                    continue;

                SetTextColor(bp.CodeIndex, bp.CodeLength, Color.White, Color.Red);
            }
            txtCode.SelectionStart = index;
        }

        private void NotifyDone(object sender, EventArgs e)
        {
            if (InvokeRequired)
            {
                Invoke(new EventHandler(NotifyDone), sender, e);
            }
            else
            {
                txtCode.ReadOnly = false;
                btnRun.Enabled = true;
                btnContinue.Enabled = false;
                btnStepInto.Enabled = false;
                btnNoDebug.Enabled = true;
            }
        }



        private void btnRun_Click(object sender, EventArgs e)
        {
            code = txtCode.Text;

            engine = new Engine();
            engine.UseCallStack = true;
            if (!Compile())
                return;

            
            btnStepInto.Enabled = true;
            btnContinue.Enabled = true;
            btnRun.Enabled = false;
            btnNoDebug.Enabled = false;
            txtCode.ReadOnly = true;
            txtOutput.Text = "";

            

            
            engine.BeginNotifyCall += new BeginCall(engine_BeginNotifyCall);
            engine.Print += new Print(engine_Print);

            

            trvAST.Nodes.Clear();
            TreeNode rootNode = trvAST.Nodes.Add("Application AST");
            FillTree(engine.GetRoot(), rootNode);
            trvAST.ExpandAll();

            debugThread = new Thread(StartDebugger);
            debugThread.Start();

        }

        private bool Compile()
        {
            try
            {
                engine.Parse(code);
                return true;
            }
            catch (Exception x)
            {
                MessageBox.Show(x.Message);
                return false;
            }
            
        }

        private void FillTree(object astNode,TreeNode parentTreeNode)
        {
            
            
            string name = "";
            if (astNode is ConsNode)
            {
                ConsNode cons = astNode as ConsNode;
                name = "(...)";
                TreeNode treeNode = parentTreeNode.Nodes.Add(name);
                treeNode.ForeColor = Color.Blue;
                foreach (object childAstNode in cons.Args)
                {
                    FillTree(childAstNode, treeNode);
                }
            }
            else
            {
                if (astNode is string)
                    name = string.Format("{0} : {1}", astNode, "string");
                if (astNode is int)
                    name = string.Format("{0} : {1}", astNode, "int");
                if (astNode is double)
                    name = string.Format("{0} : {1}", astNode, "double");
                if (astNode is SymbolNode)
                    name = string.Format("{0} : {1}", astNode, "id");
                TreeNode treeNode = parentTreeNode.Nodes.Add(name);
            }


        }

        void engine_Print(string text)
        {
            if (InvokeRequired)
            {
                Invoke(new Print(engine_Print), text);
            }
            else
            {
                txtOutput.SelectedText = text + Environment.NewLine;
            }
        }

        private string codeName = "";
        private Thread lastThread = null;
        void engine_BeginNotifyCall(StackFrame stackFrame,ConsNode cons, LispFunc func, string funcName)
        {
            if (InvokeRequired)
            {
                if (codeName != cons.CodeName && codeName != "")
                    return;

                FillSymbols();
                FillCallStack(stackFrame);
                Invoke(new BeginCall(engine_BeginNotifyCall),stackFrame ,cons, func, funcName);

                
            }
            else
            {
                propertyGrid1.SelectedObject = allSymbols;
                grdCallStack.DataSource = callStackTable;

                if (cons.CodeName != codeName)
                {
                    if (chkCurrentCodeOnly.Checked && codeName != "")
                        return;

                    codeName = cons.CodeName;
                    txtCode.Text = cons.Code;
                }

                int start = cons.CodeStartIndex+1;

                if (start < 0)
                    start = 0;

                int length = funcName.Length;
                int fullLength = cons.CodeLength;
                RemoveFormatting();
                SetTextColor(start, fullLength, Color.Black, Color.Yellow);
                ShowBreakPoints();
                SetTextColor(start, length, Color.White, Color.Blue);
                
                txtCode.SelectionLength = 0;
                txtCode.Select();
                this.Activate();

                if (!stepping)
                {
                    foreach (BreakPoint bp in breakPoints)
                    {
                        if (bp.Node == cons)
                        {
                            stepping = true;
                            break;
                        }
                    }
                }

                if (stepping)
                {
                    debugThread.Suspend();
                }
                
            }
            
            //  SetTextColor(cons.CodeStartIndex , cons.CodeLength, Color.Black, Color.Green);
        }

        
        private void FillCallStack(StackFrame stackFrame)
        {
            DataTable dt = new DataTable();
            dt.Columns.Add("Source", typeof(string));
            dt.Columns.Add("ScopeId", typeof(string));
            dt.Columns.Add("FunctionName", typeof(string));
            dt.Columns.Add("FunctionBody", typeof(string));
            StackFrame frame = stackFrame;            
            while (frame != null && frame.Cons != null)
            {
                if (stackFrame.Cons.HideFromCallstack)
                {
                }
                else
                {
                    string codeName = frame.Cons.CodeName;
                    string functionName = frame.FunctionName;
                    string desc = frame.Cons.ToString();
                    string scopeId = frame.Scope.GetHashCode ().ToString();

                    dt.Rows.Add(codeName,scopeId, functionName,desc );
                }
                frame = frame.PreviousStackFrame;
            }
            callStackTable = dt;
        }

        
        private void FillSymbols()
        {

            //List<NamedSymbol> symbols = new List<NamedSymbol>();
            //try
            //{
            //    foreach (var varStack in engine.Stack.Symbols)
            //    {
            //        string name = varStack.Key;
            //        if (varStack.Value.Count == 0)
            //            continue;

            //        object value = null;

            //        Symbol tmp = varStack.Value.Peek();


            //        if (tmp.Value != null)
            //        {
            //            value = tmp.Value.ToString();
            //        }
            //        else
            //        {
            //            value = "{null}";
            //        }



            //        //Symbol var = varStack.Value.Peek();
            //        NamedSymbol namedVar = new NamedSymbol();
            //        namedVar.Name = name;
            //        namedVar.Value = value;
            //        symbols.Add(namedVar);
            //    }
            //}
            //catch
            //{
            //}



            //allSymbols = symbols.OrderBy(key => key.Name).ToArray();

        }

        private void SetTextColor(int start,int length,Color foreColor ,Color backColor)
        {
            txtCode.SelectionStart = start;
            txtCode.SelectionLength = length;
            txtCode.SelectionColor = foreColor;
            txtCode.SelectionBackColor = backColor;
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        private void Form1_KeyDown(object sender, KeyEventArgs e)
        {            
            if (e.KeyCode == Keys.F9)
            {
                e.Handled = true;
                SetBreakPoint();
            }
            if (e.KeyCode == Keys.F10)
            {

                e.Handled = true;
                StepInto();
            }
        }

        private void SetBreakPoint()
        {
            int index = txtCode.SelectionStart;
            
            char current = txtCode.Text[index];
            while (current != '(')
            {
                index--;
                if (index < 0)
                    return; //cant set bp

                current = txtCode.Text[index];
            }

            ValueNode node = engine.Stack.GetNodeFromCodeIndex("user", index+1);
            if (node == null)
                return;



            BreakPoint existing = null;
            foreach (BreakPoint bp in breakPoints)
            {
                if (bp.Node == node)
                {
                    existing = bp;
                    break;
                }
            }

            if (existing != null)
            {
                breakPoints.Remove(existing);
            }
            else
            {
                BreakPoint bp = new BreakPoint();
                bp.CodeIndex = node.CodeStartIndex - 1;
                bp.CodeLength = 1;
                bp.CodeName = node.CodeName;
                bp.Node = node;

                breakPoints.Add(bp);
            }

            RemoveFormatting();
            ShowBreakPoints();

        }

        private void StepInto()
        {
            if (debugThread == null || !debugThread.IsAlive)
            {
                return;
            }

            if (debugThread.ThreadState == ThreadState.Suspended)
            {
                debugThread.Resume();
            }

        }


        private void btnStepInto_Click(object sender, EventArgs e)
        {
            StepInto();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            KillDebugger();
        }

        private void KillDebugger()
        {
            try
            {
                if (debugThread == null)
                    return;

                if (debugThread.IsAlive)
                {
                    if (debugThread.ThreadState == ThreadState.Suspended)
                        debugThread.Resume();

                    debugThread.Abort();
                }
            }
            catch
            {
                //ye ye i dont have time for this...
            }
        }

        private void btnContinue_Click(object sender, EventArgs e)
        {
            Continue();
        }

        private void Continue()
        {
            
            stepping = false;
            StepInto();
        }

        private void btnNoDebug_Click(object sender, EventArgs e)
        {
            code = txtCode.Text;
            btnRun.Enabled = false;
            txtCode.ReadOnly = true;
            txtOutput.Text = "";            

            engine = new Engine();            
            engine.Print += new Print(engine_Print);

            engine.Parse(code);
            trvAST.Nodes.Clear();
            TreeNode rootNode = trvAST.Nodes.Add("Application AST");
            
            trvAST.BeginUpdate();
            FillTree(engine.GetRoot(), rootNode);
            trvAST.ExpandAll();
            trvAST.EndUpdate();
            
            

            debugThread = new Thread(StartDebugger);
            debugThread.Priority = ThreadPriority.Highest;
            btnRun.Enabled = false;
            btnNoDebug.Enabled = false;

            
            debugThread.Start();  
        }
    }
}
